//
//  NSZip.m
//  NewiOSProject
//
//  Created by apple on 2023/9/29.
//  Copyright © 2023 范智渊. All rights reserved.
//

 
#import "NSZip.h"
#define DATA_CHUNK_SIZE 262144
 
@interface NSZip ()
{
    BOOL zipReady;
    z_stream zStream;
}
 
@property (atomic, assign, readonly) BOOL zipReady;
@end
 
@implementation NSZip
 
@synthesize zipReady;
 
+ (id)zip
{
    NSZip *zip = [[NSZip alloc] init];
    [zip resetZip];
    return zip;
}
 
 
- (void)dealloc
{
    if (zipReady)
    {
        [self closeZip];
    }
}
 
- (NSError *)resetZip
{
    if (zipReady)
    {
        return nil;
    }
    // Setup the inflate stream
    zStream.zalloc = Z_NULL;
    zStream.zfree = Z_NULL;
    zStream.opaque = Z_NULL;
    zStream.avail_in = 0;
    zStream.next_in = 0;
    int status = inflateInit2(&zStream, (15+32));
    if (status != Z_OK)
    {
        return [[self class] inflateErrorWithCode:status];
    }
    zipReady = YES;
    return nil;
}
 
- (NSError *)closeZip
{
    if (!zipReady)
    {
        return nil;
    }
    // Close the inflate stream
    zipReady = NO;
    int status = inflateEnd(&zStream);
    if (status != Z_OK)
    {
        return [[self class] inflateErrorWithCode:status];
    }
    return nil;
}
 
 
- (NSData *)zip:(NSData *)data error:(NSError **)err
{
    if (data && data.length > 0)
    {
        zStream.next_in = (Bytef *) data.bytes;
        zStream.avail_in = (uInt) data.length;
        zStream.total_out = 0;
        
        OSStatus status = deflateInit2(&zStream,Z_DEFAULT_COMPRESSION,Z_DEFLATED, MAX_WBITS+16,MAX_MEM_LEVEL,Z_DEFAULT_STRATEGY);
/*       历史缓冲区最大尺寸，值为 2^windowBits,
        windowBits 的值为 8~15 时，deflate() 方法生成 zlib 格式的数据，
        当 windowBits 为 31 时 deflate() 方法生成 gzip 格式。
        当取值为 -15 ~ -8 时，deflate() 生成纯 deflate 算法压缩数据（不包含 zlib 和 gzip 格式头和尾）
        OSStatus status = deflateInit2(&zStream, Z_DEFAULT_COMPRESSION,Z_DEFLATED, 31, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);*/
        
        if (status != Z_OK)
        {
            return nil;
        }
        
        NSInteger kZlibCompressChunkSize = 2048;
        NSMutableData *compressedData = [NSMutableData dataWithLength:kZlibCompressChunkSize];
        do {
            if ((status == Z_BUF_ERROR) || (zStream.total_out == compressedData.length)) {
                [compressedData increaseLengthBy:kZlibCompressChunkSize];
            }
            zStream.next_out = (Bytef *)compressedData.bytes + zStream.total_out;
            zStream.avail_out = (uInt)(compressedData.length - zStream.total_out);
            status = deflate(&zStream, Z_FINISH);
        } while ((status == Z_BUF_ERROR) || (status == Z_OK));
        
        status = deflateEnd(&zStream);
        
        if ((status != Z_OK) && (status != Z_STREAM_END)) {
            *err = [[self class] inflateErrorWithCode:status];
            return nil;
        }
        
        compressedData.length = zStream.total_out;
        
        return compressedData;
    }
    return nil;
}
 
- (NSData *)unZip:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err
{
    if (length == 0) return nil;
    
    NSUInteger halfLength = length/2;
    NSMutableData *outputData = [NSMutableData dataWithLength:length+halfLength];
 
    int status;
    
    zStream.next_in = bytes;
    zStream.avail_in = (unsigned int)length;
    zStream.avail_out = 0;
    
    NSUInteger bytesProcessedAlready = zStream.total_out;
    while (zStream.avail_in != 0)
    {
        if (zStream.total_out-bytesProcessedAlready >= [outputData length]) {
            [outputData increaseLengthBy:halfLength];
        }
        
        zStream.next_out = (Bytef*)[outputData mutableBytes] + zStream.total_out-bytesProcessedAlready;
        zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready));
        
        status = inflate(&zStream, Z_NO_FLUSH);
        
        if (status == Z_STREAM_END)
        {
            break;
        }
        else if (status != Z_OK)
        {
            if (err)
            {
                *err = [[self class] inflateErrorWithCode:status];
            }
            return nil;
        }
    }
    
    // Set real length
    [outputData setLength: zStream.total_out-bytesProcessedAlready];
    return outputData;
}
 
 
+ (NSData *)unZip:(NSData*)data error:(NSError **)err
{
    NSError *theError = nil;
    NSData *outputData = [[NSZip zip] unZip:(Bytef *)[data bytes] length:[data length] error:&theError];
    if (theError)
    {
        if (err)
        {
            *err = theError;
        }
        return nil;
    }
    return outputData;
}
 
+ (BOOL)unZip:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err
{
    NSFileManager *fileManager = [[NSFileManager alloc] init];
 
    // Create an empty file at the destination path
    if (![fileManager createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) {
        if (err)
        {
            *err = [NSError errorWithDomain:@"NSZipErrorDomain" code:11 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were to create a file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,nil]];
        }
        return NO;
    }
    
    // Ensure the source file exists
    if (![fileManager fileExistsAtPath:sourcePath])
    {
        if (err)
        {
            *err = [NSError errorWithDomain:@"NSZipErrorDomain" code:11 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed the file does not exist",sourcePath],NSLocalizedDescriptionKey,nil]];
        }
        return NO;
    }
    
    UInt8 inputData[DATA_CHUNK_SIZE];
    NSData *outputData;
    NSInteger readLength;
    NSError *theError = nil;
    
 
    NSZip *decompressor = [NSZip zip];
 
    NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath];
    [inputStream open];
    NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO];
    [outputStream open];
    
    while ([decompressor zipReady])
    {
        // Read some data from the file
        readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE];
        
        // Make sure nothing went wrong
        if ([inputStream streamStatus] == NSStreamStatusError)
        {
            if (err)
            {
                *err = [NSError errorWithDomain:@"NSZipErrorDomain" code:11 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]];
            }
            [decompressor closeZip];
            return NO;
        }
        // Have we reached the end of the input data?
        if (!readLength)
        {
            break;
        }
 
        // Attempt to inflate the chunk of data
        outputData = [decompressor unZip:inputData length:(NSUInteger)readLength error:&theError];
        if (theError)
        {
            if (err)
            {
                *err = theError;
            }
            [decompressor closeZip];
            return NO;
        }
        
        // Write the inflated data out to the destination file
        [outputStream write:(Bytef*)[outputData bytes] maxLength:[outputData length]];
        
        // Make sure nothing went wrong
        if ([inputStream streamStatus] == NSStreamStatusError) {
            if (err)
            {
                *err = [NSError errorWithDomain:@"NSZipErrorDomain" code:11 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to write to the destination data file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]];
            }
            [decompressor closeZip];
            return NO;
        }
        
    }
    
    [inputStream close];
    [outputStream close];
 
    NSError *error = [decompressor closeZip];
    if (error)
    {
        if (err)
        {
            *err = error;
        }
        return NO;
    }
 
    return YES;
}
 
 
+ (NSError *)inflateErrorWithCode:(int)code
{
    return [NSError errorWithDomain:@"NSZipErrorDomain" code:11 userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of data failed with code %d",code],NSLocalizedDescriptionKey,nil]];
}
@end
